from global_params import *
import random
import traceback
import time
      # Name,    Imm,     ZP,    ZPX,    ZPY,    ABS,   ABSX,   ABSY,    IND,   INDX,   INDY,   SNGL, BRA 
IMM  = 1 
ZP   = 2
ZPX  = 3
ZPY  = 4
ABS  = 5
ABSX = 6
ABSY = 7
IND  = 8
INDX = 9
INDY = 10
SNGL = 11
BRA  = 12
OPCODE_LEN = {
    IMM : 2,
    ZP  : 2,
    ZPX : 2,
    ZPY : 2,
    ABS : 3,
    ABSX: 3,
    ABSY: 3,
    IND : 3,
    INDX: 2,
    INDY: 2,
    SNGL: 1,
    BRA : 2
}

JMP_LIST = ['JSR','RTS']

DEBUG = False

class Emulator6502:
    def init(self):
        self.msg = "\n"
        self.mem = [0]*MEM_SIZE
        self.regPC = PC_START
        self.regA = 0
        self.regX = 0
        self.regY = 0
        self.regSP = 0xff
        #self.regFlagZ = 0       #used to judge how to set flag Z registers. Sometimes comes from regA, #sometimes comes from memory
        #self.regFlagN = 0       #used to judge how to set flag N registers. Sometimes comes from regA, #sometimes comes from memory
        #self.regFlagV = 0       #used to judge how to set flag V registers. Sometimes comes from regA, #sometimes comes from memory

        self.regTemp = 0

        self.flagC = 0      #bit 0
        self.flagZ = 0       #bit 1
        self.flagI = 0  #bit 2
        self.flagD = 0    #bit 3
        self.flagB = 0      #bit 4
        self.flagV = 0   #bit 6
        self.flagN = 0   #bit 7

        self.code_runing = 0

        self.op_map = {
           #OP_type  op             flagN           flagZ
            "ADC" : ["adc",      "regA",      "regA",      'nzc'],
            "AND" : ["xand",      "regA",      "regA",      'nz'],
            "ASL" : ["asl",      "regTemp",   "regTemp",   'nzc'], #care regA
            "BIT" : ["bit",      "regTemp",   "regA",      'nvz'],
            "BPL" : ["bpl"],
            "BMI" : ["bmi"],
            "BVC" : ["bvc"],
            "BVS" : ["bvs"],
            "BCC" : ["bcc"],
            "BCS" : ["bcs"],
            "BNE" : ["bne"],
            "BEQ" : ["beq"],
            'BRK' : ["brk"],
            "CMP" : ["cmp"],
            "CPX" : ["cpx"],
            "CPY" : ["cpy"],
            "DEC" : ["dec",      "regTemp",   "regTemp",   'nz'],
            "EOR" : ["eor",      "regA",      "regA",      'nz'],
            "CLC" : ["clc"],
            "SEC" : ["sec"],
            "CLI" : ["cli"],
            "SEI" : ["sei"],
            "CLV" : ["clv"],
            "CLD" : ["cld"],
            "SED" : ["sed"],
            "INC" : ["inc",      "regTemp",   "regTemp",   'nz'],
            "JMP" : ["jmp"],
            "JSR" : ["jsr"],
            "LDA" : ["lda",      "regA",      "regA",      'nz'],
            "LDX" : ["ldx",      "regX",      "regX",      'nz'],
            "LDY" : ["ldy",      "regY",      "regY",      'nz'],
            "LSR" : ["lsr",      "regTemp",   "regTemp",   'nz'],
            "NOP" : ["nop"],
            'ORA' : ["ora",      "regA",      "regA",      'nz'],
            "TAX" : ["tax",      "regX",      "regX",      'nz'],
            "TXA" : ["txa",      "regA",      "regA",      'nz'],
            "DEX" : ["dex",      "regX",      "regX",      'nz'],
            "INX" : ["inx",      "regX",      "regX",      'nz'],
            "TAY" : ["tay",      "regY",      "regY",      'nz'],
            "TYA" : ["tya",      "regA",      "regA",      'nz'],
            "DEY" : ["dey",      "regY",      "dey",       'nz'],
            "INY" : ["iny",      "regY",      "iny",       'nz'],
            "ROR" : ["ror",      "regTemp",   "regTemp",   'nz'], #care A
            "ROL" : ["rol",      "regTemp",   "regTemp",   'nz'], #care A
            "RTI" : ["rti"],
            "RTS" : ["rts"],
            "SBC" : ["sbc",      "regTemp",   "regTemp",   'nz'],
            "STA" : ["sta"],
            "TXS" : ["txs"],
            "TSX" : ["tsx"],
            "PHA" : ["pha"],
            "PLA" : ["pla"],
            "PHP" : ["php"],
            "PLP" : ["plp"],
            "STX" : ["stx"],
            "STY" : ["sty"]
        }


    def getStatusReg(self):
        return (self.flagN << 7) | (self.flagV << 6) | (self.flagD<<3) | \
               (self.flgaI << 2) | (self.flagZ << 1) | self.flagC

    def setStatusReg(self,val):
        self.flagN = (val >> 7) & 1
        self.flagV = (val >> 6) & 1
        self.flagD = (val >> 3) & 1
        self.flgaI = (val >> 2) & 1
        self.flagZ = (val >> 1) & 1
        self.flagC = val & 1


    def stackPush(self,value):
        self.mem[(self.regSP & 0xff) + 0x100] =  value & 0xff
        self.regSP -= 1
        if (self.regSP < 0):
            self.regSP &= 0xff
            print("6502 Stack filled! Wrapping...")

    def stackPop(self):
        val = self.mem[(self.regSP & 0xff)+0x101]
        self.regSP += 1
        if self.regSP > 0xff:
            self.regSP = 0
            print("6502 Stack empty! Wrapping...")
        return val

    def run(self):
        self.code_runing = 1
        while self.code_runing:
            self.exec_instruction()

    def reset(self):
        self.regPC = PC_START
        return self.pc_line[PC_START]

    def singleStep(self):
        self.code_runing = 1
        self.exec_instruction()
        return self.pc_line[self.regPC],self.code_runing

    def exec_instruction(self):
        t0 = time.time()
        self.opcode = self.mem[self.regPC]
        self.find_opcode(self.opcode)
        if self.op_type == 'JMP':
            self.opcode_len = 0
        else:
            self.opcode_len = OPCODE_LEN[self.addr_type]
        try:
            self.get_op_num(self.addr_type)
        except Exception as e:
            print('str(e):\t\t', str(e))
            print('repr(e):\t', repr(e))
            print('e.message:\t', e.message)
            print('traceback.print_exc():', traceback.print_exc())
            print('traceback.format_exc():\n%s' % traceback.format_exc())
            print('self.opcode=%02x,self.addr=%04x'%(self.opcode,self.addr))
            raise Exception('exit')
            
        ops = self.op_map[self.op_type]
        append_msg = "-PC = %04x opcode=%02x op=%s op_num=%02x op_num+1=%02x \
op_len=%d SP=%02x stack=%02x stack-1=%02x\n"%(
                    self.regPC, self.opcode,self.op_type,
                    self.mem[self.regPC+1],self.mem[self.regPC+2],self.opcode_len,
                    self.regSP,self.mem[self.regSP+0x101],
                    self.mem[self.regSP+0x102])
        self.msg += append_msg
        #print append_msg
        getattr(self,ops[0])()
        #print "ops=%s. regX = %02x"%(ops,self.regX)
        if(len(ops) > 1): self.set_flags(*tuple(ops[1:]))
        if self.op_type not in JMP_LIST:
            self.regPC += self.opcode_len
        self.setRand()
        if self.regPC > PC_START + len(self.code):
            self.code_runing = 0
        t1 = time.time()
        t_gap = t1 - t0 - 0.01
        if t_gap > 0:
            time.sleep(t_gap)

    def load(self,code,pc_line):
        self.pc_line = pc_line
        self.regPC = PC_START
        self.regSP = 0xff
        self.code = code
        if len(code) > PC_START:
            return "code length larger than memory"
        self.mem[PC_START: PC_START+len(code)] = code


    def find_opcode(self,opcode):
        if DEBUG:
            print("opcode=%x"%opcode)
        for line in opcodes:
            for i in range(len(line)):
                if opcode == line[i]:
                    self.addr_type = i
                    self.op_type = line[0]

    def setRand(self):
        self.mem[0xfe] = random.randint(0,0xff)

    def get_op_num(self,addr_type):
        if addr_type == IMM:
            self.op_num = self.mem[self.regPC+1]
        elif addr_type == SNGL:
            self.op_num = self.regA
        else:
            if addr_type == ZP:
                self.addr = self.mem[self.regPC+1] 
            elif addr_type == ZPX:
                self.addr = self.mem[self.regPC+1]+self.regX 
            elif addr_type == ZPY:
                self.addr = self.mem[self.regPC+1]+self.regY 
            elif addr_type == ABS:
                addr_l = self.mem[self.regPC+1]
                addr_h = self.mem[self.regPC+2]
                self.addr = (addr_h << 8) + addr_l 
                self.addr_for_jmp = self.addr
            elif addr_type == ABSX:
                addr_l = self.mem[self.regPC+1]
                addr_h = self.mem[self.regPC+2]
                self.addr = (addr_h << 8) + addr_l + self.regX
            elif addr_type == ABSY:
                addr_l = self.mem[self.regPC+1]
                addr_h = self.mem[self.regPC+2]
                self.addr = (addr_h << 8) + addr_l + self.regY
            elif addr_type == IND:
                addr_l = self.mem[self.mem[self.regPC+1]]
                addr_h = self.mem[self.mem[self.regPC+2]]
                self.addr = (addr_h << 8) + addr_l 
                if ( (self.regPC - PC_START + 1) & 0xFF == 0x00):
                    self.addr_for_jmp = self.addr - 0x100
                else:
                    self.addr_for_jmp = self.addr 
            elif addr_type == INDX:
                addr_f = self.mem[self.regPC+1] + self.regX
                addr_l = self.mem[addr_f]
                addr_h = self.mem[addr_f+1]
                self.addr = (addr_h << 8) + addr_l 
            elif addr_type == INDY:
                addr_f = self.mem[self.regPC+1] 
                addr_l = self.mem[addr_f]
                addr_h = self.mem[addr_f+1]
                self.addr = (addr_h << 8) + addr_l + self.regY
            elif addr_type == BRA:
                self.addr = self.regPC+1
                #self.addr = (self.mem[self.regPC+2] << 8) + self.mem[self.regPC+1]
            append_msg = "+PC = %04x opcode=%02x op=%s op_num=%02x op_num+1=%02x \
op_len=%d SP=%02x stack=%02x stack-1=%02x\n"%(
                        self.regPC, self.opcode,self.op_type,
                        self.mem[self.regPC+1],self.mem[self.regPC+2],self.opcode_len,
                        self.regSP,self.mem[self.regSP+0x101],
                        self.mem[self.regSP+0x102])
            #print "PC=%04x addr_type=%d addr=%04x"%(self.regPC,self.addr_type,self.addr)
            if DEBUG:
                print(append_msg)
            self.op_num = self.mem[self.addr]

    def brk(self):
        self.code_runing = 0

    def cmp_base(self,val):
        if self.op_num == val:
            self.flagZ = 1
        else:
            self.flagZ = 0
        self.flagN = (val >> 7) & 1
        if self.op_num <= val:
            self.flagC = 1
        else:
            self.flagC = 0


    def cmp(self):
        self.cmp_base(self.regA)

    def cpx(self):
        self.cmp_base(self.regX)

    def cpy(self):
        self.cmp_base(self.regY)

    def dec(self):
        self.mem[self.addr] = (self.op_num - 1) & 0xFF
        self.regTemp = self.mem[self.addr]

    def eor(self):
        self.regA ^= self.op_num

    def clc(self):
        self.flagC = 0

    def sec(self):
        self.flagC = 1

    def cli(self):
        self.flgaI = 0

    def sei(self):
        self.flgaI = 1

    def clv(self):
        self.flagV = 0

    def cld(self):
        self.flagD = 0

    def sed(self):
        self.flagD = 1

    def inc(self):
        self.mem[self.addr] = (self.op_num + 1) & 0xff
        self.regTemp = self.mem[self.addr]

    def jmp(self):
        self.regPC = self.addr_for_jmp

    def jsr(self):
        curAddr = self.regPC;
        self.stackPush((curAddr>>8)&0xff)
        self.stackPush(curAddr&0xff)
        self.regPC = (self.mem[self.regPC+1] + (self.mem[self.regPC+2]<<8))
        if DEBUG:
            print("jsr to %0x"%(self.regPC))

    def lda(self):
        self.regA = self.op_num

    def ldx(self):
        self.regX = self.op_num

    def ldy(self):
        self.regY = self.op_num

    def lsr(self):
        self.regTemp = self.op_num
        self.flagC = self.op_num&1
        self.regTemp = (self.regTemp>>1) & 0xff
        if self.addr_type == SNGL:
            self.regA = self.regTemp
        else:
            self.mem[self.addr] = self.regTemp

    def nop(self):
        pass

    def ora(self):
        self.regA |= self.op_num

    def tax(self):
        self.regX = self.regA

    def txa(self):
        self.regA = self.regX

    def dex(self):
        self.regX -= 1
        self.regX &= 0xff


    def inx(self):
        self.regX += 1
        self.regX &= 0xff

    def tay(self):
        self.regY = self.regA

    def tya(self):
        self.regA = self.regY

    def dey(self):
        self.regY -= 1

    def iny(self):
        self.regY += 1

    def ror(self):
        self.regTemp = self.op_num
        self.regTemp = ( (self.flagC<<7) | (self.regTemp>>1) ) & 0xff
        self.flagC = self.op_num&1
        if self.addr_type == SNGL:
            self.regA = self.regTemp
        else:
            self.mem[self.addr] = self.regTemp

    def rol(self):
        self.regTemp = self.op_num
        self.regTemp = ( (self.flagC) | (self.regTemp<<1) ) & 0xff
        self.flagC = (self.op_num>>7)&1
        if self.addr_type == SNGL:
            self.regA = self.regTemp
        else:
            self.mem[self.addr] = self.regTemp

    def rti(self):
        self.setStatusReg(self.stackPop())
        self.regPC = self.stackPop() | (self.stackPop() << 8)

    def rts(self):
        self.regPC = (self.stackPop() | (self.stackPop() << 8)) + 3
        if DEBUG:
            print("rts to %x"%self.regPC)

    def sbc(self):
        self.op_num = ~self.op_num & 0xff
        self.adc()


    def adc(self):
        if self.flagD:
            regA_n0 = self.regA & 0xf
            regA_n1 = self.regA & 0xf0
            op_num_n0 = self.op_num & 0xf
            op_num_n1 = self.op_num & 0xf0 
            sum_n0 = regA_n0 + op_num_n0 + self.flagC
            if sum_n0 >= 10:
                sum_n0 = 0x10 + ((sum_n0 + 6) & 0xf)
            sum_byte = regA_n1 + op_num_n1 + sum_n0
            if sum_byte >= 160:
                sum_byte += 0x60
                self.flagC = 1
                if sum_byte >= 0x180:
                    self.flagV = 0
                else:
                    self.flagV = 1
            else:
                self.flagC = 0
                if sum_byte <= 0x80:
                    self.flagV = 0
                else:
                    self.flagV = 1
        else:
            sum_byte = self.regA + self.op_num + self.flagC
            if sum_byte >= 0x100:
                self.flagC = 1
                if sum_byte >= 0x180:
                    self.flagV = 0
                else:
                    self.flagV = 1
            else:
                self.flagC = 0
                if sum_byte <= 0x80:
                    self.flagV = 0
                else:
                    self.flagV = 1
        self.regA = sum_byte & 0xff

    def xand(self):
        self.regA &= self.op_num

    def asl(self):
        self.regTemp = self.op_num
        self.flagC = (self.regTemp>>7)&1
        self.regTemp = (self.regTemp<<1) & 0xff
        if self.addr_type == SNGL:
            self.regA = self.regTemp
        else:
            self.mem[self.addr] = self.regTemp
    
    def bit(self):
        self.regTemp = self.op_num
        self.regA &= self.regTemp

    def bpl(self):
        if not self.flagN:
            self.flag_jump()

    def bmi(self):
        if self.flagN:
            self.flag_jump()

    def bvc(self):
        if not self.flagV:
            self.flag_jump()
    
    def bvs(self):
        if self.flagV:
            self.flag_jump()

    def bcc(self):
        if not self.flagC:
            self.flag_jump()

    def bcs(self):
        if self.flagC:
            self.flag_jump()

    def bne(self):
        if not self.flagZ:
            self.flag_jump()

    def beq(self):
        if self.flagZ:
            self.flag_jump()

    def sta(self):
        self.mem[self.addr] = self.regA

    def txs(self):
        self.regX = self.regSP

    def tsx(self):
        self.regSP = self.regX

    def pha(self):
        self.stackPush(self.regA)

    def pla(self):
        self.regA = self.stackPop()

    def php(self):
        self.stackPush(self.getStatusReg())

    def plp(self):
        self.setStatusReg(self.stackPop())

    def stx(self):
        self.regX = self.regX

    def sty(self):
        self.regY = self.regY

    def set_flags(self,*argv):
        if DEBUG:
            print("set_flags:",argv,"regX=%02x"%self.regX)
        if argv[2].count('n'):
            if getattr(self,argv[0])& 0x80:
                self.flagN = 1
            else:
                self.flagN = 0
        if argv[2].count('z'):
            if getattr(self,argv[0]):
                self.flagZ = 0
            else:
                self.flagZ = 1
        if argv[2].count('c'):
            if self.flagD:
                if self.regA > 100:
                    self.flagC = 1
                else:
                    self.flagC = 0
            else:
                if self.regA & 0x100:
                    self.flagC = 1
                else:
                    self.flagC = 0

    def flag_jump(self):
        if self.op_num&0x80: #negtive
            #print "op_num=%02x branch back %02x"%(self.op_num,0x100 - (self.op_num&0x7f))
            self.regPC -= (0x100 - self.op_num)
        else:
            self.regPC += self.op_num
        


